package com.xiyu.service.service.mailbox;

import com.xiyu.service.enums.mailbox.mail.MailboxMailApproveStatusEnum;
import com.xiyu.service.enums.system.notice.SystemNoticeTypeEnum;
import com.xiyu.service.model.system.notice.SystemNotice;
import com.xiyu.service.model.system.notice.SystemNoticeDraft;
import com.xiyu.service.repository.mailbox.mail.*;
import com.xiyu.service.repository.system.notice.SystemNoticeRepository;
import com.xiyu.service.vo.mailbox.mail.MailGetReceiverByAddressesOutput;
import com.xiyu.service.vo.mailbox.mail.MailGetReceiverByAddressesInput;
import com.xiyu.service.enums.mailbox.protocol.MailboxProtocolEnum;
import com.xiyu.service.framework.postOffice.sync.SyncSaveMail;
import com.xiyu.service.repository.customer.contact.CustomerContactMailRepository;
import com.xiyu.service.service.system.user.UserService;
import com.xiyu.service.util.common.CommonUtils;
import com.xiyu.service.vo.mailbox.mail.MailSaveMailAsFileOutput;
import cn.hutool.core.lang.Validator;
import com.xiyu.service.enums.mailbox.mail.MailboxMailResourceTypeEnum;
import com.xiyu.service.enums.mailbox.mail.MailboxMailStatusEnum;
import com.xiyu.service.model.infra.file.InfraFile;
import com.xiyu.service.model.mailbox.mail.*;
import com.xiyu.service.repository.infra.file.InfraFileRepository;
import com.xiyu.service.vo.mailbox.mail.MailSaveMailInput;
import com.xiyu.service.vo.mailbox.mail.MailReceiverMultiQueryOutput;
import com.xiyu.service.vo.mailbox.mail.MailReceiverMultiQueryInput;
import com.xiyu.service.vo.mailbox.mail.MailClearMailLabelInput;
import com.xiyu.service.framework.web.web.core.pojo.PageResult;
import com.xiyu.service.model.mailbox.label.MailboxLabel;
import com.xiyu.service.model.mailbox.label.MailboxLabelDraft;
import com.xiyu.service.repository.mailbox.label.MailboxLabelRepository;
import com.xiyu.service.vo.mailbox.mail.MailGetMailDetailOutput;
import com.xiyu.service.enums.mailbox.mail.MailboxMailTypeEnum;
import com.xiyu.service.framework.postOffice.factory.PostOfficeFactory;
import com.xiyu.service.framework.postOffice.service.PostOfficeService;
import com.xiyu.service.vo.mailbox.mail.MailSetMailJunkInput;
import com.xiyu.service.enums.mailbox.folder.MailboxFolderClassEnum;
import com.xiyu.service.vo.mailbox.mail.MailMoveFolderOutput;
import com.xiyu.service.vo.mailbox.mail.MailMoveFolderInput;
import com.xiyu.service.vo.mailbox.mail.MailSetMailLabelInput;
import com.xiyu.service.vo.mailbox.mail.MailSetMailTopInput;
import com.xiyu.service.vo.mailbox.mail.MailSetMailReadInput;
import com.xiyu.service.vo.mailbox.mail.MailPageMailOutput;
import com.xiyu.service.vo.mailbox.mail.MailPageMailInput;
import com.xiyu.service.vo.mailbox.mail.MailLabelSortInput;
import com.xiyu.service.model.mailbox.account.MailboxAccount;
import com.xiyu.service.repository.mailbox.account.MailboxAccountRepository;
import com.xiyu.service.vo.mailbox.mail.MailSingleGetLabelOutput;
import com.xiyu.service.vo.mailbox.mail.MailUpdateLabelInput;
import com.xiyu.service.vo.mailbox.mail.MailCreateLabelInput;
import com.xiyu.service.vo.mailbox.mail.MailListLabelOutput;
import com.xiyu.service.enums.mailbox.folder.MailboxFolderTypeEnum;
import com.xiyu.service.vo.mailbox.mail.MailUpdateFolderInput;
import com.xiyu.service.model.mailbox.folder.MailboxFolderDraft;
import com.xiyu.service.vo.mailbox.mail.MailCreateFolderInput;
import com.xiyu.service.model.mailbox.folder.MailboxFolder;
import com.xiyu.service.repository.mailbox.folder.MailboxFolderRepository;
import com.xiyu.service.vo.mailbox.mail.MailGetFolderByAccountIdsOutput;
import com.xiyu.service.convert.mailbox.MailConvert;
import jakarta.mail.MessagingException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.babyfish.jimmer.DraftObjects;
import org.babyfish.jimmer.Page;
import org.babyfish.jimmer.sql.ast.mutation.SaveMode;
import org.babyfish.jimmer.sql.ast.tuple.Tuple2;
import org.babyfish.jimmer.sql.ast.tuple.Tuple3;
import org.babyfish.jimmer.sql.ast.tuple.Tuple4;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;

import jakarta.annotation.Resource;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.xiyu.service.errorCode.mailbox.MailErrorCode.*;
import static com.xiyu.service.framework.exception.util.ServiceExceptionUtil.exception;
import static com.xiyu.service.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static com.xiyu.service.util.mail.MailUtils.extractImagePaths;

/**
 * 邮件管理 Service 实现类
 */
@Service
@Validated
@Slf4j
public class MailServiceImpl implements MailService {

    @Resource
    private MailboxAccountRepository mailboxAccountRepository;

    @Resource
    private MailboxMailRepository mailboxMailRepository;

    @Resource
    private MailboxFolderRepository mailboxFolderRepository;

    @Resource
    private MailboxMailLabelRepository mailboxMailLabelRepository;

    @Resource
    private MailboxLabelRepository mailboxLabelRepository;

    @Resource
    private MailboxMailReceiverRepository mailboxMailReceiverRepository;

    @Resource
    private MailboxMailResourceRepository mailboxMailResourceRepository;

    @Resource
    private MailboxMailContentRepository mailboxMailContentRepository;

    @Resource
    private MailboxMailErrorLogService mailboxMailErrorLogService;

    @Resource
    private InfraFileRepository infraFileRepository;

    @Resource
    private PostOfficeFactory postOfficeFactory;

    @Resource
    private SyncSaveMail syncSaveMail;

    @Resource
    private CustomerContactMailRepository customerContactMailRepository;

    @Resource
    private MailboxMailErrorLogRepository mailboxMailErrorLogRepository;

    @Resource
    private MailboxMailApproveRepository mailboxMailApproveRepository;

    @Resource
    private MailboxMailApproveReceiverRepository mailboxMailApproveReceiverRepository;

    @Resource
    private SystemNoticeRepository systemNoticeRepository;

    @Resource
    private UserService userService;


    @Override
    public List<MailGetFolderByAccountIdsOutput> getFolderByAccountIds(List<Long> accountIds) {
        return MailConvert.INSTANCE.getFolderByAccountIdsOutputConvert(mailboxFolderRepository.getMailboxFolderByAccountIds(accountIds));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createFolder(MailCreateFolderInput inputVO) {
        Optional<MailboxFolder> optionalParentFolder = mailboxFolderRepository.findById(inputVO.getParentId());
        if(optionalParentFolder.isEmpty()){
            throw exception(MAILBOX_FOLDER_NOT_EXIST);
        }
        // 一个文件夹下只能文件夹名不能重复
        Optional<MailboxFolder> optionalDupFolder = mailboxFolderRepository.findFirstByParentIdAndName(inputVO.getParentId(), inputVO.getName());
        if(optionalDupFolder.isPresent()){
            throw exception(MAILBOX_FOLDER_DUPLICATE);
        }

        MailboxFolder newFolder = MailboxFolderDraft.$.produce(draft ->
                draft.setAccountId(inputVO.getAccountId())
                    .setParentId(inputVO.getParentId())
                    .setFolderType(MailboxFolderTypeEnum.EDIT.getValue())
                    .setName(inputVO.getName())
                    .setFolderClass(Objects.equals(optionalParentFolder.get().folderClass(), MailboxFolderClassEnum.ACCOUNT.getValue())
                        ? MailboxFolderClassEnum.OTHER.getValue() : optionalParentFolder.get().folderClass()));
        // 新建邮局文件夹并返回邮局URL文件夹名
        PostOfficeService postOfficeService = postOfficeFactory.getInstance(inputVO.getAccountId());
        String postOfficeName = postOfficeService.createFolder(newFolder);
        postOfficeService.close();

        newFolder = MailboxFolderDraft.$.produce(newFolder, draft -> draft.setPostOfficeName(postOfficeName));

        return mailboxFolderRepository.insert(newFolder).id();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateFolder(MailUpdateFolderInput inputVO) {
        Optional<MailboxFolder> optionalUpdateFolder = mailboxFolderRepository.findById(inputVO.getId());
        if(optionalUpdateFolder.isEmpty()){
            throw exception(MAILBOX_FOLDER_NOT_EXIST);
        }
        if(optionalUpdateFolder.get().name().equals(inputVO.getName()))
            return true;

        // 一个文件夹下只能文件夹名不能重复
        Optional<MailboxFolder> optionalDupFolder = mailboxFolderRepository.findFirstByParentIdAndName(optionalUpdateFolder.get().parentId(), inputVO.getName());
        if(optionalDupFolder.isPresent()){
            throw exception(MAILBOX_FOLDER_DUPLICATE);
        }

        if(!optionalUpdateFolder.get().folderType().equals(MailboxFolderTypeEnum.EDIT.getValue())){
            throw exception(MAILBOX_FOLDER_ONLY_RENAME_EDIT);
        }

        MailboxFolder updateFolder = MailboxFolderDraft.$.produce(optionalUpdateFolder.get(), draft -> draft.setName(inputVO.getName()));
        // 更新邮局的文件夹名并返回新的邮局URL文件夹名
        PostOfficeService postOfficeService = postOfficeFactory.getInstance(updateFolder.accountId());
        String postOfficeName = postOfficeService.renameFolder(updateFolder);
        postOfficeService.close();

        updateFolder = MailboxFolderDraft.$.produce(updateFolder, draft -> draft.setPostOfficeName(postOfficeName));

        mailboxFolderRepository.update(updateFolder);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteFolder(Long id) {
        Optional<MailboxFolder> optionalDeleteFolder = mailboxFolderRepository.findById(id);
        if(optionalDeleteFolder.isEmpty()){
            throw exception(MAILBOX_FOLDER_NOT_EXIST);
        }
        // 删除自身和子文件夹
        deleteTreeFolder(id);

        PostOfficeService postOfficeService = postOfficeFactory.getInstance(optionalDeleteFolder.get().accountId());
        postOfficeService.deleteFolder(optionalDeleteFolder.get());
        postOfficeService.close();

        return true;
    }

    void deleteTreeFolder(Long id){
        Optional<MailboxFolder> optionalDeleteFolder = mailboxFolderRepository.findById(id);
        if(optionalDeleteFolder.isEmpty()){
            throw exception(MAILBOX_FOLDER_NOT_EXIST);
        }
        if(!optionalDeleteFolder.get().folderType().equals(MailboxFolderTypeEnum.EDIT.getValue())){
            throw exception(MAILBOX_FOLDER_ONLY_DELETE_EDIT);
        }
        List<MailboxFolder> childFolders = mailboxFolderRepository.findByParentId(id);
        for(MailboxFolder childFolder : childFolders){
            deleteTreeFolder(childFolder.id());
        }
        mailboxFolderRepository.deleteById(id);
    }

    @Override
    public List<MailListLabelOutput> listLabel(Long ownerId) {
        List<MailboxLabel> labels = mailboxLabelRepository.findByOwnerId(ownerId);
        return MailConvert.INSTANCE.listLabelOutputConvert(labels);
    }

    private MailboxFolder getRootLabelFolder(Long accountId){
        Optional<MailboxFolder> optionalRootFolder = mailboxFolderRepository.findRootFolder(accountId);
        if(optionalRootFolder.isEmpty())
            throw exception(MAILBOX_ROOT_FOLDER_NOT_EXIST);
        Optional<MailboxFolder> optionalParentFolder = mailboxFolderRepository.findRootLabel(optionalRootFolder.get().id());
        if(optionalParentFolder.isEmpty())
            throw exception(MAILBOX_ROOT_FOLDER_NOT_EXIST);
        return optionalParentFolder.get();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createLabel(MailCreateLabelInput inputVO) {
        Long userId = inputVO.getOwnerId();

        // 插入标签
        Optional<MailboxLabel> optionalExistsLabel = mailboxLabelRepository.findFirstByOwnerIdAndName(userId, inputVO.getName());
        if(optionalExistsLabel.isPresent()){
            throw exception(MAILBOX_MAIL_LABEL_NAME_DUPLICATE);
        }
        MailboxLabel newLabel = MailboxLabelDraft.$.produce(draft -> draft.setColor(inputVO.getColor()).setName(inputVO.getName()).setOwnerId(userId));
        newLabel = mailboxLabelRepository.insert(newLabel);
        mailboxLabelRepository.updateSort(newLabel.id(), newLabel.id());

        // 插入标签文件夹
        List<MailboxAccount> accounts = mailboxAccountRepository.findByOwnerId(userId);
        Long newFolderId = 0L;
        for (MailboxAccount account : accounts){
            MailboxFolder rootLabelFolder = getRootLabelFolder(account.id());
            MailboxFolder folder = MailboxFolderDraft.$.produce(draft -> draft.setName(inputVO.getName()).setFolderType(MailboxFolderTypeEnum.LABEL.getValue())
                    .setColor(inputVO.getColor())
                    .setParentId(rootLabelFolder.id()).setAccountId(account.id())
                    .setFolderClass(MailboxFolderClassEnum.LABEL.getValue()));
            folder = mailboxFolderRepository.insert(folder);
            mailboxFolderRepository.updateSort(folder.id(), folder.id());
        }
        return newFolderId;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateLabel(MailUpdateLabelInput inputVO) {
        Long userId = inputVO.getOwnerId();

        Optional<MailboxLabel> optionalOldLabel = mailboxLabelRepository.findById(inputVO.getId());
        if(optionalOldLabel.isEmpty()){
            throw exception(MAILBOX_MAIL_LABEL_NOT_EXIST);
        }

        Optional<MailboxLabel> optionalDupLabel = mailboxLabelRepository.findFirstByOwnerIdAndName(userId, inputVO.getName());
        if(optionalDupLabel.isPresent() && optionalDupLabel.get().id() != optionalOldLabel.get().id()){
            throw exception(MAILBOX_MAIL_LABEL_NAME_DUPLICATE);
        }
        MailboxLabel updateLabel = MailboxLabelDraft.$.produce(optionalOldLabel.get(), draft -> draft.setName(inputVO.getName()).setColor(inputVO.getColor()));
        mailboxLabelRepository.update(updateLabel);


        List<MailboxAccount> accounts = mailboxAccountRepository.findByOwnerId(userId);
        for (MailboxAccount account : accounts){
            getRootLabelFolder(account.id());

            Optional<MailboxFolder> optionalOldLabelFolder = mailboxFolderRepository.findLabelByName(account.id(), optionalOldLabel.get().name());
            if(optionalOldLabelFolder.isEmpty()){
                throw exception(MAILBOX_MAIL_LABEL_NOT_EXIST);
            }

            MailboxFolder label = MailboxFolderDraft.$.produce(optionalOldLabelFolder.get(), draft -> draft.setName(inputVO.getName()).setColor(inputVO.getColor()));
            mailboxFolderRepository.update(label);
        }

        return true;
    }


    @Override
    public MailSingleGetLabelOutput singleGetLabel(Long id) {
        Optional<MailboxLabel> optionalMailboxLabel = mailboxLabelRepository.findById(id);
        if(optionalMailboxLabel.isEmpty()){
            throw exception(MAILBOX_MAIL_LABEL_NOT_EXIST);
        }
        return MailConvert.INSTANCE.singleGetLabelOutputConvert(optionalMailboxLabel.get());
    }

    private void updateLabelFolderSort(MailLabelSortInput inputVO){
        Long userId = inputVO.getOwnerId();
        List<MailboxAccount> accounts = mailboxAccountRepository.findByOwnerId(userId);
        for (MailboxAccount account : accounts){
            MailboxFolder rootLabelFolder = getRootLabelFolder(account.id());

            List<MailboxFolder> labelFolders = mailboxFolderRepository.findByParentId(rootLabelFolder.id());
            if(labelFolders.size() < inputVO.getOldIndex() || labelFolders.size() < inputVO.getNewIndex())
                continue;

            List<Pair<Long, Long>> oldSortList = labelFolders.stream().map(labelFolder -> Pair.of(labelFolder.id(), labelFolder.sort())).collect(Collectors.toList());

            List<Pair<Long, Long>> newSortList = CommonUtils.dragSort(inputVO.getOldIndex(), inputVO.getNewIndex(), oldSortList);
            for(Pair<Long, Long> sort : newSortList){
                mailboxFolderRepository.updateSort(sort.getLeft(), sort.getRight());
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean labelSort(MailLabelSortInput inputVO) {
        Long userId = inputVO.getOwnerId();
        Optional<MailboxLabel> optionalLabel = mailboxLabelRepository.findById(inputVO.getId());
        if(optionalLabel.isEmpty()){
            throw exception(MAILBOX_MAIL_LABEL_NOT_EXIST);
        }

        List<MailboxLabel> labels = mailboxLabelRepository.findByOwnerId(userId);
        if(labels.size() < inputVO.getOldIndex() || labels.size() < inputVO.getNewIndex())
            return true;

        List<Pair<Long, Long>> oldSortList = labels.stream().map(label -> Pair.of(label.id(), label.sort())).collect(Collectors.toList());

        List<Pair<Long, Long>> newSortList = CommonUtils.dragSort(inputVO.getOldIndex(), inputVO.getNewIndex(), oldSortList);
        for(Pair<Long, Long> sort : newSortList){
            mailboxLabelRepository.updateSort(sort.getLeft(), sort.getRight());
        }

        updateLabelFolderSort(inputVO);

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteLabel(Long id) {
        Optional<MailboxLabel> optionalLabel = mailboxLabelRepository.findById(id);
        if(optionalLabel.isEmpty()){
            throw exception(MAILBOX_MAIL_LABEL_NOT_EXIST);
        }

        List<MailboxAccount> accounts = mailboxAccountRepository.findByOwnerId(optionalLabel.get().ownerId());
        for (MailboxAccount account : accounts){
            getRootLabelFolder(account.id());

            Optional<MailboxFolder> optionalDeleteLabelFolder = mailboxFolderRepository.findLabelByName(account.id(), optionalLabel.get().name());
            if(optionalDeleteLabelFolder.isEmpty()){
                throw exception(MAILBOX_MAIL_LABEL_NOT_EXIST);
            }
            mailboxMailLabelRepository.deleteByLabelFolderId(optionalDeleteLabelFolder.get().id());
            mailboxFolderRepository.delete(optionalDeleteLabelFolder.get());
        }

        mailboxLabelRepository.delete(optionalLabel.get());
        return true;
    }

    @Override
    public Long getUnreadTopMail(List<Long> accountIds) {
        return mailboxMailRepository.unreadTopCount(accountIds);
    }

    private List<Long> getCurrentUserAllLabelFolder(Long ownerId){
        List<Long> accounts = mailboxAccountRepository.findIdByOwnerId(ownerId);
        List<MailboxFolder> labelFolderList = mailboxFolderRepository.getMailboxFolderByAccountIdsAndFolderType(accounts, MailboxFolderTypeEnum.LABEL.getValue());
        return labelFolderList.stream().map(MailboxFolder::id).collect(Collectors.toList());
    }

    private List<Long> getCurrentUserAllSendFolder(Long ownerId){
        List<Long> accounts = mailboxAccountRepository.findIdByOwnerId(ownerId);
        List<MailboxFolder> sendFolderList = mailboxFolderRepository.getMailboxFolderByAccountIdsAndFolderClass(accounts, MailboxFolderClassEnum.SEND.getValue());
        return sendFolderList.stream().map(MailboxFolder::id).collect(Collectors.toList());
    }

    private List<Long> getCurrentUserAllReceiveFolder(Long ownerId){
        List<Long> accounts = mailboxAccountRepository.findIdByOwnerId(ownerId);
        List<MailboxFolder> sendFolderList = mailboxFolderRepository.getMailboxFolderByAccountIdsAndFolderClass(accounts, MailboxFolderClassEnum.RECEIVE.getValue());
        return sendFolderList.stream().map(MailboxFolder::id).collect(Collectors.toList());
    }

    private List<Long> getAllLabelFolderByCommonLabelId(Long commonLabelId){
        Optional<MailboxLabel> optionalLabel = mailboxLabelRepository.findById(Math.abs(commonLabelId));
        if(optionalLabel.isEmpty())
            return null;

        List<MailboxFolder> folders = mailboxFolderRepository.findByFolderTypeAndName(MailboxFolderTypeEnum.LABEL.getValue(), optionalLabel.get().name());
        return folders.stream().map(MailboxFolder::id).collect(Collectors.toList());
    }

    private PageResult<MailPageMailOutput> pageApprove(MailPageMailInput inputVO){
        String strFolderId = inputVO.getFolderId();
        if(strFolderId.equals("commonApprove"))
            return new PageResult<>(new ArrayList<>(),  0L);
        List<Integer> statuses = new ArrayList<>();
        Long creatorId = null, receiverId = null;
        if(strFolderId.equals("commonApprovePending")){
            statuses.add(MailboxMailApproveStatusEnum.PENDING_APPROVAL.getValue());
            receiverId = inputVO.getOwnerId();
        }
        if(strFolderId.equals("commonApproveHandle")){
            statuses.add(MailboxMailApproveStatusEnum.REVERT.getValue());
            statuses.add(MailboxMailApproveStatusEnum.DONE.getValue());
            receiverId = inputVO.getOwnerId();
        }
        if(strFolderId.equals("commonApproveMy")){
            creatorId = getLoginUserId();
        }
        Page<MailboxMailApprove> approvePage = mailboxMailApproveRepository.pageMail(inputVO, statuses, creatorId, receiverId);
        List<MailPageMailOutput> approveList = MailConvert.INSTANCE.pageMailApproveOutPutConvert(approvePage.getRows());
        return new PageResult<>(approveList,  approvePage.getTotalRowCount());
    }

    @Override
    public PageResult<MailPageMailOutput> pageMail(MailPageMailInput inputVO) {
        // type : 0 普通文件夹 1 标签文件夹 2 所有未读 3 置顶邮件 4 所有邮件
        int type = 0;
        Long folderId;
        String strFolderId = inputVO.getFolderId();
        List<Long> labelFolderIds = new ArrayList<>();
        List<Long> folderIds = new ArrayList<>();
        List<Long> accountIds = new ArrayList<>();
        if(strFolderId.equals("commonUnread")){
            type = 2;
        }else if(strFolderId.equals("commonTop")){
            type = 3;
        }else if(strFolderId.equals("commonLabel")){
            type = 1;
            labelFolderIds = getCurrentUserAllLabelFolder(inputVO.getOwnerId());
            accountIds = mailboxAccountRepository.findIdByOwnerId(inputVO.getOwnerId());
        }else if(strFolderId.equals("commonAll")){
            type = 4;
            accountIds = mailboxAccountRepository.findIdByOwnerId(inputVO.getOwnerId());
        }else if(strFolderId.equals("commonSend")){
            folderIds = getCurrentUserAllSendFolder(inputVO.getOwnerId());
        }else if(strFolderId.equals("commonReceive")){
            folderIds = getCurrentUserAllReceiveFolder(inputVO.getOwnerId());
        }else if(strFolderId.equals("common")){
            return new PageResult<>(new ArrayList<>(),  0L);
        }else if(strFolderId.contains("commonApprove")){
            return pageApprove(inputVO);
        }else if(strFolderId.contains("commonChildLabel-")){
            type = 1;
            Long commonLabelFolderId = Long.parseLong(strFolderId.substring(strFolderId.indexOf("commonChildLabel-") + "commonChildLabel-".length()));
            labelFolderIds = getAllLabelFolderByCommonLabelId(commonLabelFolderId);
            accountIds = mailboxAccountRepository.findIdByOwnerId(inputVO.getOwnerId());
        }else{
            folderIds.add(Long.parseLong(strFolderId));
        }
        if(folderIds.size() > 0){
            folderId = folderIds.get(0);
            Optional<MailboxFolder> optionalFolder = mailboxFolderRepository.findById(folderId);
            if(optionalFolder.isEmpty()){
                throw exception(MAILBOX_FOLDER_NOT_EXIST);
            }
            accountIds.add(optionalFolder.get().accountId());
            // 点击的文件夹如果是标签文件夹 要按type = 2处理
            if(optionalFolder.get().folderType().equals(MailboxFolderTypeEnum.LABEL.getValue())){
                type = 1;
                Optional<MailboxFolder> optionalParentFolder = mailboxFolderRepository.findById(optionalFolder.get().parentId());
                // 如果是label文件夹就显示子文件夹下所有标签的邮件
                if(optionalParentFolder.isPresent() && optionalParentFolder.get().parentId().equals(0L)){
                    List<MailboxFolder> labels = mailboxFolderRepository.findByParentId(optionalFolder.get().id());
                    labelFolderIds =  labels.stream().map(MailboxFolder::id).collect(Collectors.toList());
                }else{
                    labelFolderIds.add(optionalFolder.get().id());
                }
            }
        }
        Page<MailboxMail> mailPage = mailboxMailRepository.getMailByFolderId(inputVO, type, accountIds, folderIds, labelFolderIds);
        List<MailPageMailOutput> outputs = MailConvert.INSTANCE.pageMailOutPutConvert(mailPage.getRows());
        return new PageResult<>(outputs, mailPage.getTotalRowCount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean setMailRead(MailSetMailReadInput inputVO) {
        setMailRead(inputVO.getIds(), inputVO.getIsRead());
        return true;
    }

    private void setMailRead(List<Long> mailIds, boolean isRead){
        List<MailboxMail> mailList = mailboxMailRepository.findByIdLock(mailIds);
        mailList = mailList.stream().filter(mail -> mail.isRead() != isRead).collect(Collectors.toList());
        mailIds = mailList.stream().map(MailboxMail::id).collect(Collectors.toList());

        HashMap<Long, List<MailboxMail>> accountMailHashMap = new HashMap<>();
        for (MailboxMail mail : mailList){
            mailboxFolderRepository.updateReadMailCount(mail.folderId(), isRead);
            if(accountMailHashMap.containsKey(mail.accountId())){
                accountMailHashMap.get(mail.accountId()).add(mail);
            }else{
                List<MailboxMail> mails = new ArrayList<>();
                mails.add(mail);
                accountMailHashMap.put(mail.accountId(), mails);
            }
        }
        mailboxMailRepository.updateMailRead(mailIds, isRead);

        // 更新标签文件夹
        updateLabelFolderReadCount(mailIds, isRead);

        // 通知邮局
        for(Map.Entry<Long, List<MailboxMail>> entry : accountMailHashMap.entrySet()){
            PostOfficeService postOfficeService = postOfficeFactory.getInstance(entry.getKey());
            postOfficeService.setMailRead(entry.getValue(), isRead);
            postOfficeService.close();
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean setMailTop(MailSetMailTopInput inputVO) {
        List<MailboxMail> mailList = mailboxMailRepository.findByIdLock(inputVO.getIds());
        // 更新标签文件夹
        updateLabelFolderTopCount(inputVO.getIds(), inputVO.getIsTop());

        HashMap<Long, List<MailboxMail>> accountMailHashMap = new HashMap<>();
        for(MailboxMail mail : mailList){
            if(!mail.isTop().equals(inputVO.getIsTop())){
                mailboxFolderRepository.updateTopMailCount(mail.folderId(), inputVO.getIsTop());
            }
            if(accountMailHashMap.containsKey(mail.accountId())){
                accountMailHashMap.get(mail.accountId()).add(mail);
            }else{
                List<MailboxMail> mails = new ArrayList<>();
                mails.add(mail);
                accountMailHashMap.put(mail.accountId(), mails);
            }
        }

        mailboxMailRepository.updateMailTop(inputVO.getIds(), inputVO.getIsTop());
        // 通知邮局
        for(Map.Entry<Long, List<MailboxMail>> entry : accountMailHashMap.entrySet()){
            PostOfficeService postOfficeService = postOfficeFactory.getInstance(entry.getKey());
            postOfficeService.setMailTop(entry.getValue(), inputVO.getIsTop());
            postOfficeService.close();
        }
        return true;
    }

    @Override
    public Boolean setMailLabel(MailSetMailLabelInput inputVO) {
        for(Long mailId : inputVO.getIds()){
            Optional<MailboxMail> optionalMail = mailboxMailRepository.findById(mailId);
            if(optionalMail.isEmpty()){
                throw exception(MAILBOX_MAIL_NOT_EXIST);
            }
            MailboxFolder label;
            if(StringUtils.hasText(inputVO.getLabelName())) {
                Optional<MailboxFolder> optionalLabel = mailboxFolderRepository.findLabelByName(optionalMail.get().accountId(), inputVO.getLabelName());
                if (optionalLabel.isEmpty()) {
                    throw exception(MAILBOX_LABEL_NOT_EXIST);
                }
                label = optionalLabel.get();
            }else{
                throw exception(MAILBOX_LABEL_NOT_EXIST);
            }
            Optional<MailboxMailLabel> optionalMailboxMailLabel = mailboxMailLabelRepository.findByMailIdAndLabelFolderId(optionalMail.get().id(), label.id());
            if(optionalMailboxMailLabel.isEmpty() && inputVO.getIsAdd()){
                updateLabelFolderCount(optionalMail.get(), label, inputVO.getIsAdd());
                MailboxMailLabel newMailLabel = MailboxMailLabelDraft.$.produce(draft -> draft.setLabelFolderId(label.id()).setMailId(optionalMail.get().id()));
                mailboxMailLabelRepository.insert(newMailLabel);
            }

            if(optionalMailboxMailLabel.isPresent() && !inputVO.getIsAdd()){
                updateLabelFolderCount(optionalMail.get(), label, inputVO.getIsAdd());
                mailboxMailLabelRepository.deleteById(optionalMailboxMailLabel.get().id());
            }

        }

        return true;
    }

    private void updateLabelFolderTopCount(List<Long> mailIds, Boolean isTop){
        for(Long mailId : mailIds){
            Optional<MailboxMail> optionalMail = mailboxMailRepository.findById(mailId);
            if(optionalMail.isEmpty()){
                throw exception(MAILBOX_MAIL_NOT_EXIST);
            }
            if(optionalMail.get().isTop().equals(isTop))
                continue;
            List<MailboxMailLabel> mailboxMailLabels = mailboxMailLabelRepository.findByMailId(mailId);
            for(MailboxMailLabel mailboxMailLabel : mailboxMailLabels){
                Optional<MailboxFolder> optionalLabel = mailboxFolderRepository.findById(Objects.requireNonNull(mailboxMailLabel.labelFolderId()));
                if(optionalLabel.isEmpty()){
                    throw exception(MAILBOX_LABEL_NOT_EXIST);
                }
                MailboxFolder label = MailboxFolderDraft.$.produce(optionalLabel.get(), draft -> draft.setTopMailCount(draft.topMailCount() + (isTop ? 1 : -1)));
                mailboxFolderRepository.update(label);
            }
        }
    }

    private void updateLabelFolderReadCount(List<Long> mailIds, Boolean isRead){
        List<Long> accountIds = new ArrayList<>();
        for(Long mailId : mailIds){
            Optional<MailboxMail> optionalMail = mailboxMailRepository.findById(mailId);
            if(optionalMail.isEmpty()){
                throw exception(MAILBOX_MAIL_NOT_EXIST);
            }
            if(!accountIds.contains(optionalMail.get().accountId())){
                accountIds.add(optionalMail.get().accountId());
            }
            List<MailboxMailLabel> mailboxMailLabels = mailboxMailLabelRepository.findByMailId(mailId);
            for(MailboxMailLabel mailboxMailLabel : mailboxMailLabels){
                Optional<MailboxFolder> optionalLabelFolder = mailboxFolderRepository.findById(Objects.requireNonNull(mailboxMailLabel.labelFolderId()));
                if(optionalLabelFolder.isEmpty()){
                    throw exception(MAILBOX_LABEL_NOT_EXIST);
                }
                MailboxFolder labelFolder = MailboxFolderDraft.$.produce(optionalLabelFolder.get(), draft -> draft.setReadMailCount(draft.readMailCount() + (isRead ? 1 : -1)));
                mailboxFolderRepository.update(labelFolder);
            }
        }

        for(Long accountId : accountIds){
            MailboxFolder rootLabelFolder = getRootLabelFolder(accountId);
            Long accountLabelMailCount = mailboxMailLabelRepository.getAccountLabelMailCount(accountId);
            Long accountLabelUnreadMailCount = mailboxMailLabelRepository.getAccountLabelUnreadMailCount(accountId);
            rootLabelFolder = MailboxFolderDraft.$.produce(rootLabelFolder,
                    draft -> draft.setMailCount(accountLabelMailCount.intValue()).setReadMailCount(accountLabelUnreadMailCount.intValue())
            );
            mailboxFolderRepository.update(rootLabelFolder);
        }
    }

    private void updateLabelFolderCount(MailboxMail mail, MailboxFolder label, Boolean isAdd){
        int addCount = isAdd ? 1 : -1;
        MailboxFolder updateLabel = MailboxFolderDraft.$.produce(label, draft -> draft.setMailCount(draft.mailCount() + addCount)
                .setTopMailCount(draft.topMailCount() + (mail.isTop() ? addCount : 0))
                .setReadMailCount(draft.readMailCount() + (mail.isRead() ? addCount : 0)));
        mailboxFolderRepository.update(updateLabel);

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<MailMoveFolderOutput> moveFolder(MailMoveFolderInput inputVO) {
        List<MailMoveFolderOutput> outputs = new ArrayList<>();
        Optional<MailboxFolder> optionalTargetFolder = mailboxFolderRepository.findById(inputVO.getTargetFolderId());
        if(optionalTargetFolder.isEmpty()){
            throw exception(MAILBOX_FOLDER_NOT_EXIST);
        }
        PostOfficeService postOfficeService = postOfficeFactory.getInstance(optionalTargetFolder.get().accountId());
        for(Long mailId: inputVO.getMailIds()){
            Optional<MailboxMail> optionalMail = mailboxMailRepository.findById(mailId);
            if(optionalMail.isEmpty()){
                throw exception(MAILBOX_MAIL_NOT_EXIST);
            }
            if(!Objects.equals(optionalMail.get().accountId(), optionalTargetFolder.get().accountId())){
                outputs.add(new MailMoveFolderOutput(mailId, "邮件和移入的文件夹不属于同一个账号"));
                continue;
            }

            MailboxFolder sourceFolder = moveFolderUpdateSourceFolder(optionalMail.get());
            moveFolderUpdateTargetFolder(optionalMail.get(), optionalTargetFolder.get());
            String targetUid = postOfficeService.moveMailFolder(sourceFolder, optionalTargetFolder.get(), optionalMail.get());
            mailboxMailRepository.updateMailFolderId(optionalMail.get().id(), inputVO.getTargetFolderId(),
                    optionalTargetFolder.get().folderClass().equals(MailboxFolderClassEnum.JUNK.getValue()) ? optionalMail.get().folderId() : 0L,
                    targetUid);

        }
        postOfficeService.close();
        return outputs;
    }

    private MailboxFolder moveFolderUpdateSourceFolder(MailboxMail mail){
        Optional<MailboxFolder> optionalSourceFolder = mailboxFolderRepository.findById(Objects.requireNonNull(mail.folderId()));
        if(optionalSourceFolder.isEmpty()){
            throw exception(MAILBOX_FOLDER_NOT_EXIST);
        }
        mailboxFolderRepository.updateCount(mail.folderId(), -1, (mail.isRead() ? -1 : 0), (mail.isTop() ? -1 : 0));
        return optionalSourceFolder.get();
    }

    private void moveFolderUpdateTargetFolder(MailboxMail mail, MailboxFolder newFolder){
        mailboxFolderRepository.updateCount(newFolder.id(), 1, (mail.isRead() ? 1 : 0), (mail.isTop() ? 1 : 0));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean setMailJunk(MailSetMailJunkInput inputVO) {
        for(Long mailId: inputVO.getMailIds()){
            Optional<MailboxMail> optionalMail = mailboxMailRepository.findById(mailId);
            if(optionalMail.isEmpty()){
                throw exception(MAILBOX_MAIL_NOT_EXIST);
            }
            PostOfficeService postOfficeService = postOfficeFactory.getInstance(optionalMail.get().accountId());
            MailboxFolder sourceFolder = moveFolderUpdateSourceFolder(optionalMail.get());

            // 如果是设置为垃圾邮件 查找垃圾邮件文件夹 如果设置为不是垃圾邮件 查找beforeJunkFolderId
            MailboxFolder targetFolder;
            if(inputVO.getIsJunk()){
                List<MailboxFolder> JunkFolders = mailboxFolderRepository.findByAccountIdAndFolderTypeAndFolderClass(optionalMail.get().accountId(), MailboxFolderTypeEnum.INTERNAL.getValue(), MailboxFolderClassEnum.JUNK.getValue());
                if(JunkFolders.size() == 0){
                    throw exception(MAILBOX_FOLDER_CLASS_NOT_EXIST, "垃圾箱");
                }else{
                    targetFolder = JunkFolders.get(0);
                }
            }else{
                Optional<MailboxFolder> optionalFolder = mailboxFolderRepository.findById(optionalMail.get().beforeJunkFolderId());
                if(optionalFolder.isPresent()){
                    targetFolder = optionalFolder.get();
                }else{
                    Integer folderClass;
                    String folderClassName;
                    if(optionalMail.get().mailType().equals(MailboxMailTypeEnum.SEND.getValue())){
                        folderClass = MailboxFolderClassEnum.SEND.getValue();
                        folderClassName = "已发送邮件";
                    }
                    else if(optionalMail.get().mailType().equals(MailboxMailTypeEnum.RECEIVE.getValue())){
                        folderClass = MailboxFolderClassEnum.RECEIVE.getValue();
                        folderClassName = "收件箱";
                    }
                    else{
                        folderClass = MailboxFolderClassEnum.DRAFT.getValue();
                        folderClassName = "草稿箱";
                    }
                    List<MailboxFolder> folders = mailboxFolderRepository.findByAccountIdAndFolderTypeAndFolderClass(optionalMail.get().accountId(), MailboxFolderTypeEnum.INTERNAL.getValue(), folderClass);
                    if(folders.size() == 0){
                        throw exception(MAILBOX_FOLDER_CLASS_NOT_EXIST, folderClassName);
                    }else{
                        targetFolder = folders.get(0);
                    }
                }
            }
            String uid = postOfficeService.moveMailFolder(sourceFolder, targetFolder, optionalMail.get());
            moveFolderUpdateTargetFolder(optionalMail.get(), targetFolder);
            mailboxMailRepository.updateMailFolderId(optionalMail.get().id(), targetFolder.id(),
                    targetFolder.folderClass().equals(MailboxFolderClassEnum.JUNK.getValue()) ? optionalMail.get().folderId() : 0L, uid);

            postOfficeService.close();
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteMail(List<Long> mailIds) {
        for(Long mailId: mailIds) {
            Optional<MailboxMail> optionalMail = mailboxMailRepository.findById(mailId);
            if(optionalMail.isEmpty()){
                throw exception(MAILBOX_MAIL_NOT_EXIST);
            }
            // 只有在草稿箱和已删除邮件文件夹的邮件才会真删除，其它文件夹的删除是移动到已删除文件夹
            MailboxFolder sourceFolder = moveFolderUpdateSourceFolder(optionalMail.get());
            PostOfficeService postOfficeService = postOfficeFactory.getInstance(optionalMail.get().accountId());
            if(sourceFolder.folderClass().equals(MailboxFolderClassEnum.DELETED.getValue()) ||
                    sourceFolder.folderClass().equals(MailboxFolderClassEnum.DRAFT.getValue())){
                postOfficeService.deleteMail(sourceFolder, optionalMail.get());
                mailboxMailRepository.deleteById(mailId);
            }else{
                MailboxFolder targetFolder;
                List<MailboxFolder> folders = mailboxFolderRepository.findByAccountIdAndFolderTypeAndFolderClass(optionalMail.get().accountId(), MailboxFolderTypeEnum.INTERNAL.getValue(), MailboxFolderClassEnum.DELETED.getValue());
                if(folders.size() == 0){
                    throw exception(MAILBOX_FOLDER_CLASS_NOT_EXIST, "已删除邮件");
                }else{
                    targetFolder = folders.get(0);
                }
                moveFolderUpdateTargetFolder(optionalMail.get(), targetFolder);
                String uid = postOfficeService.moveMailFolder(sourceFolder, targetFolder, optionalMail.get());
                mailboxMailRepository.updateMailFolderId(optionalMail.get().id(), targetFolder.id(), 0L, uid);
            }
            postOfficeService.close();
        }
        return true;
    }

    @Override
    public MailGetMailDetailOutput getMailDetail(Long id) {
        Optional<MailboxMail> optionalMail = mailboxMailRepository.getMailInfoById(id);
        if(optionalMail.isEmpty()){
            throw exception(MAILBOX_MAIL_NOT_EXIST);
        }
        if(!optionalMail.get().isReceiveContent()){
            PostOfficeService postOfficeService = postOfficeFactory.getInstance(optionalMail.get().accountId());
            Optional<MailboxFolder> optionalFolder = mailboxFolderRepository.findById(Objects.requireNonNull(optionalMail.get().folderId()));
            if(optionalFolder.isEmpty()){
                throw exception(MAILBOX_FOLDER_NOT_EXIST);
            }
            String content = postOfficeService.getMailContent(optionalFolder.get(), optionalMail.get());
            String searchContent = postOfficeService.getMailSearchContent(optionalMail.get(), content);
            postOfficeService.close();
            List<MailboxMailResource> resources = mailboxMailResourceRepository.findByMailIdAndResourceType(id, MailboxMailResourceTypeEnum.ATTACHMENT.getValue());
            mailboxMailRepository.updateMailContent(id, content, searchContent, !resources.isEmpty());
            optionalMail= mailboxMailRepository.getMailInfoById(id);
            if(optionalMail.isEmpty()){
                throw exception(MAILBOX_MAIL_NOT_EXIST);
            }
        }

        setMailRead(Collections.singletonList(id), true);

        return MailConvert.INSTANCE.getMailDetailOutputConvert(optionalMail.get());
    }

    @Override
    public Boolean setFolderAllMailRead(Long id) {
        List<Long> mailIds =  mailboxMailRepository.getUnreadMailByFolderId(id);
        setMailRead(mailIds, true);
        return true;
    }

    @Override
    public Boolean clearMailLabel(MailClearMailLabelInput inputVO) {
        for(Long mailId : inputVO.getIds()){
            Optional<MailboxMail> optionalMail = mailboxMailRepository.findById(mailId);
            if(optionalMail.isEmpty()){
                throw exception(MAILBOX_MAIL_NOT_EXIST);
            }
            List<MailboxMailLabel> mailboxMailLabels = mailboxMailLabelRepository.findByMailId(mailId);
            for(MailboxMailLabel mailLabel : mailboxMailLabels){
                Optional<MailboxFolder> optionalMailboxLabelFolder = mailboxFolderRepository.findById(Objects.requireNonNull(mailLabel.labelFolderId()));
                if(optionalMailboxLabelFolder.isEmpty()){
                    throw exception(MAILBOX_LABEL_NOT_EXIST);
                }
                updateLabelFolderCount(optionalMail.get(), optionalMailboxLabelFolder.get(), false);
                mailboxMailLabelRepository.deleteById(mailLabel.id());
            }

        }

        return true;
    }

    @Override
    public List<MailReceiverMultiQueryOutput> receiverMultiQuery(MailReceiverMultiQueryInput inputVO) {
        List<Long> ownerUserIds = userService.getOwnerUserIds(getLoginUserId());
        List<Tuple4<String, String, Long, Integer>> output = new ArrayList<>();
        List<Tuple2<String, String>> mailboxMailReceivers = mailboxMailReceiverRepository.listMultiQuery(inputVO.getMultiOption(), ownerUserIds);
        List<Tuple3<String, String, Long>> publicReceiver = customerContactMailRepository.publicListByMulti(inputVO.getMultiOption());
        List<Tuple3<String, String, Long>> privateReceiver = customerContactMailRepository.privateListByMulti(inputVO.getMultiOption(), ownerUserIds);
        Set<String> uniqueAddress = new TreeSet<>();
        for(Tuple3<String, String, Long> receiver : privateReceiver){
            output.add(new Tuple4<>(receiver.component1(), receiver.component2(), receiver.component3(), 1));
            uniqueAddress.add(receiver.component2());
        }
        for(Tuple3<String, String, Long> receiver : publicReceiver){
            if(!uniqueAddress.contains(receiver.component2()))
                output.add(new Tuple4<>(receiver.component1(), receiver.component2(), receiver.component3(), 2));
            else{
                uniqueAddress.add(receiver.component2());
            }
        }
        for(Tuple2<String, String> receiver : mailboxMailReceivers){
            if(!uniqueAddress.contains(receiver.component2()))
                output.add(new Tuple4<>(receiver.component1(), receiver.component2(),  0L, 3));
        }
        // 1 私海客户 2 公海客户 3 陌生人
        return MailConvert.INSTANCE.receiverQueryOutputConvert(output);
    }

    @Transactional(rollbackFor = Exception.class)
    Long saveMailTransactional(MailSaveMailInput inputVO){
        if(inputVO.getId() != 0){
            MailboxMail oldMail = mailboxMailRepository.findNullable(inputVO.getId());
            if(!Objects.equals(oldMail.accountId(), inputVO.getAccountId())){
                inputVO.setId(0L);
            }
        }

        MailboxMail newMail = MailConvert.INSTANCE.saveMailInputConvert(inputVO);
        //将收件人 抄送人 密送人放到allReceivers
        List<MailboxMailReceiver> allReceivers = new ArrayList<>();
        for(MailboxMailReceiver receiver : newMail.receivers()){
            if(!Validator.isEmail(receiver.address())){
                throw exception(MAILBOX_MAIL_ADDRESS_ILLEGALITY, "收件人", receiver.address());
            }
            receiver = MailboxMailReceiverDraft.$.produce(receiver, draft -> draft.setType(0));
            allReceivers.add(receiver);
        }
        for(MailboxMailReceiver receiver : newMail.carbonReceivers()){
            if(!Validator.isEmail(receiver.address())){
                throw exception(MAILBOX_MAIL_ADDRESS_ILLEGALITY, "抄送人", receiver.address());
            }
            receiver = MailboxMailReceiverDraft.$.produce(receiver, draft -> draft.setType(1));
            allReceivers.add(receiver);
        }
        for(MailboxMailReceiver receiver : newMail.blindReceivers()){
            if(!Validator.isEmail(receiver.address())){
                throw exception(MAILBOX_MAIL_ADDRESS_ILLEGALITY, "密送人", receiver.address());
            }
            receiver = MailboxMailReceiverDraft.$.produce(receiver, draft -> draft.setType(2));
            allReceivers.add(receiver);
        }

        // 查找邮箱账号
        MailboxAccount account = mailboxAccountRepository.findNullable(inputVO.getAccountId());
        if(account == null){
            throw exception(MAILBOX_ACCOUNT_NOT_EXIST);
        }
        PostOfficeService postOfficeService = postOfficeFactory.getInstance(account.id());

        // 定义发件人
        List<MailboxMailFrom> froms = new ArrayList<>();
        froms.add(MailboxMailFromDraft.$.produce(draft -> draft.setAddress(account.mailboxAccount())
                .setName(account.sendName())));
        newMail = MailboxMailDraft.$.produce(newMail, draft -> draft.setFroms(froms));

        // 根据邮箱账号查找草稿箱
        List<MailboxFolder> folders = mailboxFolderRepository.findByAccountIdAndFolderTypeAndFolderClass(account.id(),
                MailboxFolderTypeEnum.INTERNAL.getValue(), MailboxFolderClassEnum.DRAFT.getValue());

        if(folders.size() == 0){
            throw exception(MAILBOX_FOLDER_CLASS_NOT_EXIST, "草稿箱");
        }
        MailboxFolder mailboxFolder = folders.get(0);

        // 查找邮件图片资源
        ArrayList<String> imagePaths = extractImagePaths(Objects.requireNonNull(newMail.content()).content());
        List<InfraFile> files = infraFileRepository.findByUrls(imagePaths);
        List<MailboxMailResource> inlineResource = new ArrayList<>();
        for(InfraFile file : files){
            inlineResource.add(MailboxMailResourceDraft.$.produce(draft ->
                            draft.setResourceType(MailboxMailResourceTypeEnum.INLINE.getValue())
                                    .setFileName(file.name()).setUrlName(file.url()).setSize(file.size())
                    )
            );
        }

        MailboxMail finalNewMail = newMail;
        MailboxMail mail = MailboxMailDraft.$.produce(draft ->
                draft.setAccountId(account.id()).setCalendar("").setSubject(finalNewMail.subject())
                        .setFolderId(mailboxFolder.id()).setSenderName("") .setSenderAddress("")
                        .setSendTime(LocalDateTime.now()).setReceiveTime(LocalDateTime.now()).setFroms(froms).setIsRead(true)
                        .setSize(0L).setMessageId("").setIsTop(false)
                        .setMailType(MailboxMailTypeEnum.DRAFT.getValue()).setStatus(MailboxMailStatusEnum.DRAFT.getValue())
                        .setUid("").setIsReceiveContent(true).setIsScheduled(finalNewMail.isScheduled()).setPlanSendTime(finalNewMail.planSendTime())
                        .setContent(
                                MailboxMailContentDraft.$.produce(contentDraft -> contentDraft.setContent(Objects.requireNonNull(finalNewMail.content()).content())
                                        .setSearchContent(postOfficeService.getMailSearchContent(finalNewMail, Objects.requireNonNull(finalNewMail.content()).content())))
                        ).setAllReceivers(allReceivers).setReceiverCount(allReceivers.size())
                        .setResources(Stream.concat(finalNewMail.resources().stream(), inlineResource.stream()).collect(Collectors.toList()))
        );
        if(inputVO.getId() == 0){
            mail = mailboxMailRepository.insert(mail);
        }else{
            // 更新收件人
            updateMailReceiver(newMail.receivers(), inputVO.getId(),0);
            updateMailReceiver(newMail.carbonReceivers(), inputVO.getId(),1);
            updateMailReceiver(newMail.blindReceivers(), inputVO.getId(),2);

            // 更新附件
            updateMailAttachmentResource(newMail.resources(), inputVO.getId());
            updateMailInlineResource(inlineResource, inputVO.getId());

            // 更新正文
            mailboxMailContentRepository.updateContent(inputVO.getId(), newMail.content().content(), postOfficeService.getMailSearchContent(newMail, newMail.content().content()));

            mail = MailboxMailDraft.$.produce(mail, draft -> {
                draft.setAllReceivers(Collections.emptyList()).setResources(Collections.emptyList()).setId(inputVO.getId());
                DraftObjects.unload(draft, MailboxMailProps.CONTENT);
                DraftObjects.unload(draft, MailboxMailProps.FROMS);
            });
            mailboxMailRepository.update(mail);
        }

        // 发送邮件
        if(inputVO.getType() == 0){
            postOfficeService.sendMail(mail.id());
        }
        if(inputVO.getType() == 1){
            // 只有第一次新建的时候保存 邮件不支持更新邮件草稿 异步保存
            if(inputVO.getId() == 0 && account.mailboxProtocolType().equals(MailboxProtocolEnum.IMAP.getValue())){
                syncSaveMail.saveMail(mail);
            }
        }
        if(inputVO.getType() == 2){
            List<Long> leaderIds = userService.getLeaderUserIds(getLoginUserId());
            if(leaderIds.isEmpty()){
                throw exception(MAILBOX_FOLDER_CLASS_NOT_EXIST, "草稿箱");
            }
            MailboxMail finalMail = mail;
            MailboxMailApprove approve = mailboxMailApproveRepository.insert(MailboxMailApproveDraft.$.produce(
                    draft -> draft.setMailId(finalMail.id()).setStatus(MailboxMailApproveStatusEnum.PENDING_APPROVAL.getValue())
            ));
            List<MailboxMailApproveReceiver> receivers = new ArrayList<>();

            String nickName = userService.getUser(getLoginUserId()).orElse(null).nickname();
            for(Long leaderId : leaderIds){
                receivers.add(MailboxMailApproveReceiverDraft.$.produce(draft -> draft.setApproveId(approve.id()).setReceiverId(leaderId)));
                SystemNotice notice = SystemNoticeDraft.$.produce(draft -> draft.setIsRead(false).setContent("您有一条邮件审批待处理：" + finalMail.subject() + "-" +  nickName  +"，请点击处理")
                        .setNotifierId(leaderId).setTitle("").setStatus(0).setType(SystemNoticeTypeEnum.MAIL.getValue()));
                systemNoticeRepository.insert(notice);
            }
            mailboxMailApproveReceiverRepository.saveEntities(receivers, SaveMode.INSERT_ONLY);



        }
        postOfficeService.close();
        return mail.id();
    }


    @Override
    public Long saveMail(MailSaveMailInput inputVO) {
        return saveMailTransactional(inputVO);
    }

    private void updateMailReceiver(List<MailboxMailReceiver> newReceivers, Long mailId, int type){
        List<MailboxMailReceiver> oldReceivers = mailboxMailReceiverRepository.findByMailIdAndType(mailId, type);
        if(oldReceivers.isEmpty() && newReceivers.isEmpty())
            return;
        if(newReceivers.isEmpty()){
            mailboxMailReceiverRepository.deleteByIds(oldReceivers.stream().map(MailboxMailReceiver::id).collect(Collectors.toList()));
            return;
        }
        for(MailboxMailReceiver newReceiver : newReceivers){
            Optional<MailboxMailReceiver> matchReceivers = oldReceivers.stream().filter(receiver -> Objects.equals(receiver.address(), newReceiver.address())).findFirst();
            if(matchReceivers.isEmpty()){
                mailboxMailReceiverRepository.insert(
                        MailboxMailReceiverDraft.$.produce(newReceiver, draft -> draft.setMailId(mailId).setType(type))
                );
            }else{
                if(!Objects.equals(matchReceivers.get().name(), newReceiver.name())){
                    mailboxMailReceiverRepository.updateName(mailId, newReceiver.name());
                }
            }
        }
        for(MailboxMailReceiver oldReceiver : oldReceivers){
            Optional<MailboxMailReceiver> matchReceivers = newReceivers.stream().filter(receiver -> Objects.equals(receiver.address(), oldReceiver.address())).findFirst();
            if(matchReceivers.isEmpty()){
                mailboxMailReceiverRepository.deleteById(oldReceiver.id());
            }
        }

    }

    private void updateMailInlineResource(List<MailboxMailResource> newResources, Long mailId){
        List<MailboxMailResource> oldResources = mailboxMailResourceRepository.findByMailIdAndResourceType(mailId, MailboxMailResourceTypeEnum.INLINE.getValue());
        if(oldResources.isEmpty() && newResources.isEmpty())
            return;
        if(newResources.isEmpty()){
            mailboxMailResourceRepository.deleteByIds(oldResources.stream().map(MailboxMailResource::id).collect(Collectors.toList()));
            return;
        }
        for(MailboxMailResource newResource : newResources){
            if(oldResources.stream().noneMatch(oldResource -> oldResource.urlName().equals(newResource.urlName()))){
                mailboxMailResourceRepository.insert(MailboxMailResourceDraft.$.produce(newResource, draft -> draft.setMailId(mailId)));
            }
        }
        for(MailboxMailResource oldResource : oldResources){
            if(newResources.stream().noneMatch(newResource -> newResource.urlName().equals(oldResource.urlName()))){
                mailboxMailResourceRepository.deleteById(oldResource.id());
            }
        }
    }

    private void updateMailAttachmentResource(List<MailboxMailResource> newResources, Long mailId){
        List<MailboxMailResource> oldResources = mailboxMailResourceRepository.findByMailIdAndResourceType(mailId, MailboxMailResourceTypeEnum.ATTACHMENT.getValue());
        if(oldResources.isEmpty() && newResources.isEmpty())
            return;
        if(newResources.isEmpty()){
            mailboxMailResourceRepository.deleteByIds(oldResources.stream().map(MailboxMailResource::id).collect(Collectors.toList()));
            return;
        }
        if(oldResources.isEmpty()){
            for(MailboxMailResource newResource : newResources){
                mailboxMailResourceRepository.insert(
                        MailboxMailResourceDraft.$.produce(newResource, draft -> draft.setMailId(mailId))
                );
            }
            return;
        }
        int index = 0, diffIndex = -1;
        for(;index < oldResources.size(); index ++){
            if(newResources.size() <= index
            || (!newResources.get(index).fileName().equals(oldResources.get(index).fileName()) && !newResources.get(index).urlName().equals(oldResources.get(index).urlName()))){
                if(diffIndex == -1){
                    diffIndex = index;
                }
                mailboxMailResourceRepository.deleteById(oldResources.get(index).id());
            }
        }
        if(diffIndex == -1){
            diffIndex = index;
        }
        for(;diffIndex < newResources.size(); diffIndex++){
            mailboxMailResourceRepository.insert(
                    MailboxMailResourceDraft.$.produce(newResources.get(diffIndex), draft -> draft.setMailId(mailId))
            );
        }
    }

    @Override
    public MailSaveMailAsFileOutput saveMailAsFile(Long id) {
        MailboxMail mail = mailboxMailRepository.findNullable(id);
        if(mail == null){
            throw exception(MAILBOX_MAIL_NOT_EXIST);
        }

        String url;
        try {
            PostOfficeService postOfficeService = postOfficeFactory.getInstance(mail.accountId());
            url = postOfficeService.saveMailAsFile(mail);
        } catch (MessagingException | IOException e) {
            mailboxMailErrorLogService.saveMailErrorInfo(mail.accountId(), mail.folderId(), mail.id(), mail.uid(), e);
            throw exception(MAILBOX_MAIL_SAVE_FILE_UNKNOWN_ERROR);
        }
        String fileName = Objects.equals(mail.subject(), "") ? "export.eml" : mail.subject() + ".eml";
        Optional<InfraFile> optionalInfraFile = infraFileRepository.findFirstByUrl(url);
        int size = optionalInfraFile.isEmpty() ? 0 : optionalInfraFile.get().size();
        return new MailSaveMailAsFileOutput(size, url, fileName);
    }

    @Override
    public List<MailGetReceiverByAddressesOutput> getReceiverByAddresses(List<MailGetReceiverByAddressesInput> inputVO) {
        List<Long> ownerUserIds =  userService.getOwnerUserIds(getLoginUserId());
        List<MailGetReceiverByAddressesOutput> outputs = new ArrayList<>();
        for(MailGetReceiverByAddressesInput input : inputVO){
            // 1 私海客户 2 公海客户 3 陌生人
            List<Tuple3<String, String, Long>> privateInfos = customerContactMailRepository.privateListByAddress(input.getAddress(), input.getName(), ownerUserIds);
            if(!privateInfos.isEmpty()){
                outputs.add(new MailGetReceiverByAddressesOutput(privateInfos.get(0).component1(), privateInfos.get(0).component3(), 1, privateInfos.get(0).component2()));
                continue;
            }
            List<Tuple3<String, String, Long>> publicInfos = customerContactMailRepository.publicListByAddress(input.getAddress(), input.getName());
            if(!publicInfos.isEmpty()){
                outputs.add(new MailGetReceiverByAddressesOutput(publicInfos.get(0).component1(), publicInfos.get(0).component3(), 2, publicInfos.get(0).component2()));
                continue;
            }
            List<Tuple2<String, String>> ReceiverInfos = mailboxMailReceiverRepository.listQueryByAddress(input.getAddress(), input.getName(), ownerUserIds);
            if(!ReceiverInfos.isEmpty()){
                outputs.add(new MailGetReceiverByAddressesOutput(ReceiverInfos.get(0).component1(), 0L, 3, ReceiverInfos.get(0).component2()));
                continue;
            }
            outputs.add(new MailGetReceiverByAddressesOutput(input.getName(), 0L, 3, input.getAddress()));
        }
        return outputs;
    }

    @Override
    public void scheduleSendMail(){
        List<MailboxAccount> accounts = mailboxAccountRepository.findAll();
        for(MailboxAccount account : accounts){
            List<Long> mailIds = mailboxMailRepository.findScheduleMail(account.id());
            if(mailIds.size() <= 0)
                continue;
            PostOfficeService postOfficeService = postOfficeFactory.getInstance(account.id());
            for(Long mailId : mailIds){
                try {
                    scheduleSendMailTransactional(postOfficeService, mailId);
                }catch(Exception e){
                    mailboxMailErrorLogService.saveMailErrorInfo(account.id(), 0L, mailId, "", e);
                }
            }
            postOfficeService.close();
        }

    }

    @Transactional(rollbackFor = Exception.class)
    void scheduleSendMailTransactional(PostOfficeService postOfficeService, Long mailId){
        postOfficeService.sendMail(mailId);
    }

}
